package qfpay

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"crypto/md5"
	"github.com/pkg/errors"
)

type Auth struct {
	qfAppCode   string
	appsecret   string
	requrl      string
}

type HeadParams struct {
	qfAppCode   string    //
	signature   string    //
}

func NewSDK(restURL, qfAppCode, appsecret string) *Auth {
	return &Auth{requrl: restURL, qfAppCode: qfAppCode, appsecret: appsecret}
}

func (this *Auth) invoke(params map[string]string, methodType string) (map[string]interface{}, error) {
	respMap, err := this.request(params, methodType)
	return respMap, err
}

func (this *Auth) request(params map[string]string, methodType string) (respMap map[string]interface{}, err error) {
	sign, e := this.reqsign(params)
	if e != nil {
		err = e
		return
	}
	args := HeadParams{
		qfAppCode:  this.qfAppCode,
		signature:  sign,
	}
	urlParams := url.Values{}
	for k, v := range params {
		urlParams.Add(k, v)
	}
	var req *http.Request
	client := &http.Client{}
	body := strings.NewReader(urlParams.Encode())
	req, err = http.NewRequest(methodType, this.requrl, body)
	if err != nil {
		return
	}
	req.Header.Add("X-QF-APPCODE", args.qfAppCode)
	req.Header.Add("X-QF-SIGN", args.signature)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded") //必须
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	//resCode = resp.StatusCode
	defer resp.Body.Close()
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	err = json.Unmarshal(bodyBytes, &respMap)
	if err != nil {
		err = errors.New(fmt.Sprintf("%s %s", err.Error(), string(bodyBytes)))
	}
	return
}

func (this *Auth) reqsign(fields map[string]string) (string, error) {
    basestring := ""
	var keys []string
	for k := range fields {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		basestring += k + "=" + fields[k] + "&"
	}
	midstring := strings.TrimSuffix(basestring, "&") + this.appsecret
	sign := fmt.Sprintf("%X", md5.Sum([]byte(midstring)))
	return sign, nil
}

func (this *Auth) Execute(params map[string]interface{}) (respMap map[string]interface{}, err error) {
	var paramsMap map[string]string
	paramsMap = make(map[string]string)

	for k, v := range params {
		if s, ok := v.(string); ok {
			paramsMap[k] = s
		} else if _, ok := v.(int); ok {
			paramsMap[k] = strconv.Itoa(v.(int))
		} else {
			err = errors.New("格式错误，map 格式只支持string, int")
			return
		}
	}
	respMap, err = this.invoke(paramsMap, "POST")
	return
}
